Activities of "maliming"

Hi,

Quick answers to your questions:

  1. Yes, on 9.3.x this head-of-line blocking is the expected behavior. The inbox is processed strictly in CreationTime order, and when a handler throws, the default policy rethrows so the same earliest event is picked up again on the next cycle. Events that were processed before the failing one are already committed (each runs in its own transaction), so it's the failing event and everything after it that stays stuck.

2 & 3. There was no built-in "skip and continue" or max-retry/dead-letter for the inbox in 9.3.5. We added exactly this in ABP 10.0 via InboxProcessorFailurePolicy (abpframework/abp#23563). You configure it on AbpEventBusBoxesOptions:

Configure<AbpEventBusBoxesOptions>(options =>
{
    options.InboxProcessorFailurePolicy = InboxProcessorFailurePolicy.RetryLater;
    options.InboxProcessorMaxRetryCount = 10;       // default
    options.InboxProcessorRetryBackoffFactor = 10;  // default
});

The three policies:

  • Retry (default) — keeps strict order, retries the failing event next cycle. This is the head-of-line blocking you're seeing.
  • RetryLater — skips the failing event and keeps processing the rest, then retries the failed one after a delay. The delay is factor * 2^retryCount (so 10s, 20s, 40s … with the defaults). After InboxProcessorMaxRetryCount it's discarded. This is the one that fixes your A-blocks-B case: A is pushed out to a later retry, B gets processed in the meantime, and when A comes back its data is ready.
  • Discard — drops the failing event immediately.

This needs an upgrade to ABP 10.x. The inbox table schema changed in 10.0 (the old Processed/ProcessedTime columns became Status/HandledTime plus new RetryCount/NextRetryTime), so after upgrading you'll add an EF Core migration for it. If you have any custom IEventInbox implementation, note that the interface also got RetryLaterAsync and MarkAsDiscardAsync in 10.0, so that would need updating too.

  1. RetryLater solves the inbox blocking, but I wouldn't lean on throw-to-retry as the main mechanism for a real causal dependency between events from two independent services. With the defaults the total retry window is bounded (roughly a couple of hours before discard), so if B can ever be delayed longer than that, A gets dropped. The more robust pattern is to make the dependent handler order-insensitive: when A arrives and its data isn't there yet, persist a small "pending" record and return normally instead of throwing, then complete that work when B's handler runs (or have a background worker reconcile pending items). Keep RetryLater as a safety net for genuinely transient failures, not as the way to order causally-dependent events.

Thanks

Hi,

Yes, building your own SaaS product on top of ABP Commercial and selling it to multiple end-user organizations, cloud-hosted or on-premise, is a supported scenario under your Business license (same for Enterprise). The license is granted per developer, not per end user, customer organization, or deployment, so the number of customers and servers your application runs on isn't what's licensed.

The thing to keep in mind is what the license covers: the ABP framework itself (the PRO packages, source code, templates, tools, and modules). The application you build on top of it is your own product. So:

  • Allowed: deploying or delivering your application to your customers to run, both cloud and on-premise. This includes shipping the compiled ABP runtime DLLs your application needs to run, since your customers receive your application to use, not ABP as a framework to develop with.
  • Not allowed: giving a third party the ABP Commercial packages, source code, templates, tools, or modules so they can use them as a development framework, reuse them in their own projects, sublicense them, or publish them as open source.

Based on what you described (your end users only use your application and never get ABP itself as a development framework), this is the normal, intended use of ABP Commercial.

When the license expires

ABP licenses are perpetual. Your already-built applications keep running and you can keep developing them with the ABP packages and code you already have, no renewal required. What changes after expiration:

  • You can't create new projects from the PRO startup templates (existing projects keep working).
  • PRO modules/themes already in your solution keep working, but you can't download/install new ones with the expired license, and you only get updates within your current MINOR version (e.g. on v12.0.x you still get 12.0.x patches, but not 12.1.0 or 13.x).
  • You can't use ABP Suite, and you lose the PRO features of ABP Studio (the Community Edition keeps working).
  • Premium support ends.

Renewing restores all of these and adds another year to your expiry date.

About auditing

The inspection right in the license agreement is about your use as the license holder, your facilities, and your records, to verify your compliance with the license (for example, the number of developer seats in use). Your end customers receive your application to use, not ABP as a licensed development framework, so the inspection right is directed at you, not at them.

Thanks

Hi,

1. AbpAccountOpenIddictOptions is the correct one for Angular + OpenIddict.

AbpAccountOptions.IsTenantMultiDomain / GetTenantDomain are only read by the MVC Razor Pages account flow (and the old IdentityServer path), so they have no effect on a separated OpenIddict auth server. The LinkLogin / SwitchTenant token grants read AbpAccountOpenIddictOptions, which is exactly why configuring that one works for you. It's the right, supported options class here — it's just missing from the docs, and we'll improve the docs for this option.

Configure<AbpAccountOpenIddictOptions>(options =>
{
    options.IsTenantMultiDomain = true;
    options.GetTenantDomain = (httpContext, info) =>
        Task.FromResult(/* tenant URL */);
});

2. The duplicate tenant_domain is a bug, and removing the handler is the right workaround.

When IsTenantMultiDomain is on, the grant sets tenant_domain on the sign-in AuthenticationProperties. OpenIddict already promotes that into the token response on its own, so the extra LinkLoginExtensionGrantProcessJsonResponse handler adds the same key a second time, and AddParameter throws on the duplicate — that's the HTTP 500. The handler is redundant, which is why removing it keeps tenant_domain in the response and everything works.

For the affected version, use this as a temporary workaround. If your version registers both handlers, remove both — they both hook the token response and key off the same tenant_domain parameter, so leaving the SwitchTenant one in can still throw on a LinkLogin response:

using OpenIddict.Server;
using Volo.Abp.Account.Web.ExtensionGrants;

PreConfigure<OpenIddictServerBuilder>(builder =>
{
    builder.RemoveEventHandler(LinkLoginExtensionGrantProcessJsonResponse.Descriptor);
    builder.RemoveEventHandler(SwitchTenantExtensionGrantExtensionGrantProcessJsonResponse.Descriptor);
});

We're removing these redundant handlers in the next release, so once you upgrade to a version that includes the fix you can drop the PreConfigure block.

3. Passing the token in the URL is the built-in behavior.

For a cross-subdomain switch the source and target tenants are different origins, so localStorage can't be shared between them. The Angular LinkLoginHandler transfers the token once through the redirect URL, the destination reads it into localStorage, then removes it from the visible URL and reloads. That's the framework's own handler, so there's nothing custom to maintain on your side.

There isn't a drop-in "more secure" variant in the Angular template. The redirect still passes through the address bar / browser history / any proxy or log in between, so if your security requirements are stricter than that, the only real alternative is a different architecture — a shared cookie on the parent domain, or a backend-for-frontend that keeps the tokens server-side. For the built-in flow, keeping it on HTTPS across your own subdomains is the practical setup.

Thanks

Hi,

Glad that cleared it up!

A couple of these you can already get on top of today, and the rest are fair points I'll pass on to the Suite team.

Custom code is preserved on regeneration — Suite has a custom-code feature aimed exactly at the "I edited the generated class and lost it on regen" problem. Once you enable custom code support, the code you put inside the custom-code hook points (the suite-custom-code-block-N markers Suite drops into the generated files as comments) and your overrides in the generated .Extended page classes (e.g. OnGetAsync / OnPostAsync) are respected and won't be overwritten on the next generation. Full list of hook points and how to add your own: https://abp.io/docs/latest/suite/customizing-the-generated-code . If an override stops being called after a regen, check that it lives in the .Extended file and that its signature still matches the base — that's usually what's gone stale.

Nullable warnings on string properties — these come from the entity definition, not the generated file. A string property is generated non-nullable + [NotNull] when it's marked Required, and string? + [CanBeNull] when it isn't. So suppressing the warning in the generated code gets overwritten on regen, but setting Required / nullable correctly per property in the Suite entity designer makes the warning go away and survives regeneration.

Seed/test GUIDs changing on every regenerate — right, the generated test data and data-seed records use a fresh GUID per run, so hand-tuned tests get churned. The way to avoid the rework today is to not edit the generated DataSeedContributor / generated test files — keep your own seed and test data in separate files Suite doesn't touch. The stable-GUID idea is good feedback — I'll raise it with the team.

Duplicate namespaces on regenerate — yep, that's a real gap on the generation side, I've noted it for the Suite team.

"Ask AI to add a property" inside Suite — Suite itself doesn't do AI-driven generation today. The ABP Studio AI Agent is a separate workflow that can edit your project code directly, so that's already doable through the Agent — just not part of the Suite regenerate step.

Appreciate the detailed write-up — genuinely useful.

Thanks

Hi,

The short answer on the cold-start side: full IL trimming and WASM AOT won't work cleanly with ABP, so they're not the lever to pull here. ABP's runtime is reflection- and proxy-heavy by design — convention-based DI scans module assemblies/types/attributes at startup, UoW/auditing/validation interception goes through Castle DynamicProxy, and DTO serialization over the client proxies is reflection-based System.Text.Json (a plain DefaultJsonTypeInfoResolver, no source-generated JsonSerializerContext). The ~490 warnings you measured in Castle.DynamicProxy / Autofac / Volo.Abp land on exactly that surface. We don't ship trimmer-safety metadata ([DynamicDependency], ILLink.Descriptors.xml, [DynamicallyAccessedMembers], [RequiresUnreferencedCode]) for those paths, so those warnings aren't noise you can wave through — they're real, unannotated trim risks, including the silent DTO-drop case you're worried about.

Going through your list:

| Area | Where it lands | |------|------| | A. PublishTrimmed (WASM) | Don't. No trimmer-safety metadata is shipped; convention-based DI scanning, Castle interception and reflection-based STJ all rely on metadata the trimmer can remove. | | A3. DTO round-trip under trimming | Not a safe boundary. Serialization is reflection-based and ABP doesn't root the DTO contracts, so round-trips aren't guaranteed. There's no ABP-provided descriptor or source-gen context to root them. | | A4. Trim runtime only, keep Volo.Abp.* / Castle.* whole | You can root them with TrimmerRootAssembly, but that mainly turns trimming back off for the risky ABP surface — it preserves metadata/code rather than making the reflection paths trim-safe. The remaining saving comes from the framework and other non-rooted assemblies, and it's not a config we test, so you'd own the validation. | | B. RunAOTCompilation (WASM) | Same trim-analysis problem, plus Castle-based interception and any dynamic HTTP client-proxy path use runtime proxy generation, so AOT isn't a clean switch for this stack. The static Pro client proxies avoid the dynamic HTTP-proxy generation, but they still use the same reflection-based JSON and ABP startup surface. | | C. PublishSingleFile (host) | Not a useful shape here. The executable won't actually carry the Blazor/static payload as one file — static web assets, wwwroot content, generated _framework assets and the compressed files stay as sidecar files next to it. Use the normal folder/container publish output for the host. | | C7. WasmEnableWebcil | Keep the default (true) — that's the right packaging. | | D. Bounded ICU / custom culture shard | Not something ABP provides or tests. The template keeps BlazorWebAssemblyLoadAllGlobalizationData on because the WASM client gets its culture from the server and switches among the configured languages at runtime; keep it on unless you fully own and test a custom culture set. | | E. BlazorWebAssemblyLazyLoad for Pro modules | Won't work for the Pro modules. Blazor lazy loading can defer ordinary route assemblies, but the modules in the [DependsOn] graph are startup dependencies — their types have to be available before ABP startup runs, since each module's ConfigureServices executes at app init. So admin modules can't be split out per route while they're ABP modules in the startup graph. | | F. Compressed static assets behind a proxy | Works. ABP 10 uses MapAbpStaticAssets, which serves the ABP virtual-file/dynamic bundle files and then calls ASP.NET Core MapStaticAssets. Serving notes below. | | G. Server-side PublishReadyToRun | Fine for the host (R2R doesn't strip metadata). Publish it for your deploy RID (-r linux-x64). Just don't combine the host with trimming or NativeAOT — same reflection reasons as the WASM side. |

A concrete set you can pin. These split across two projects — the WASM knobs go in the client project, not the host:

<!-- host project (Microsoft.NET.Sdk.Web) -->
<PropertyGroup>
  <PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>
<!-- WASM client project (Microsoft.NET.Sdk.BlazorWebAssembly) -->
<PropertyGroup>
  <BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
  <WasmEnableWebcil>true</WasmEnableWebcil>
  <!-- Do NOT enable PublishTrimmed, RunAOTCompilation or InvariantGlobalization for the ABP client. -->
</PropertyGroup>

SatelliteResourceLanguages is worth setting only if you're intentionally limiting UI satellite resources to a fixed set of languages — with Language Management and runtime language switching in the picture, don't blanket-pin it to en.

Where the cold-start wins actually are for your stack (no reflection risk):

  • Keep WebCIL + Brotli (already produced by dotnet publish -c Release). Serve the precompressed _framework/*.br directly and let the client negotiate; don't recompress at runtime.
  • Behind a reverse proxy, either Kestrel or the proxy can serve the precompressed framework files — pick one, just don't double-compress. Serve the fingerprinted WASM assets with Cache-Control: public, max-age=31536000, immutable; the filenames change on rebuild, so long immutable caching is safe.
  • ABP's runtime-generated bundles (the LeptonX global bundles) are written into the dynamic file provider at startup and served through ABP's virtual/static-file middleware, not through the publish-time precompressed-asset pipeline — so treat those as normal static responses and let the proxy/Kestrel compress them if you want them compressed.
  • Make sure HTTP/2 (or HTTP/3) is on at the proxy so the ~180 _framework requests multiplex on one connection instead of paying per-request overhead.

So the lever for your setup is transport (WebCIL + Brotli + immutable caching + HTTP/2), not trimming/AOT.

Thanks

Hi,

Your analysis is correct — the Idle Session Timeout is a client-side idle sign-out for an unattended browser, and that's intentional. It doesn't track last activity on the server and isn't a server-side idle token-revocation mechanism, so while the user keeps interacting the session runs on the normal token lifetime. Fair point that the docs don't spell this boundary out; we'll make it clearer.

To bound how long an issued token stays usable, the lever is the token lifetime (access and refresh), not the idle setting. You can configure it here:

https://abp.io/docs/latest/modules/openiddict#setting-tokens-lifetime

Note that's an absolute lifetime, not a server-side sliding idle timeout.

Thanks

Hi,

Good questions. Here's how the AI Agent actually works under the hood, which covers most of these.

1) Connecting your own Copilot account / your own key

Not supported at the moment. The AI Agent runs on AI credits provisioned per organization, and there's no bring-your-own-key or "use my Copilot / OpenAI / Anthropic account" option. The credentials are managed on our side and pushed to Studio at runtime, so you can't swap in your own key.

2) Is your code shared or used by abp.io / Volosoft?

The Agent runs locally in ABP Studio. For a normal completion, your prompt and the code context it carries go from Studio straight to the LLM provider that serves the model you selected — it isn't sent to a Volosoft-hosted LLM endpoint. The chat history Studio keeps is stored locally on your machine, per solution.

How we handle your data is stated in the Privacy Notice in the AI Assistant panel:

Your chat history is securely stored to enhance your experience within ABP Studio, such as continuing previous conversations or improving support quality. We do not sell, share, or use your chat data for marketing or any other external purposes.

.abpignore is used to keep sensitive files out of the context — by default it already excludes appsettings.secrets.json, .env*, *.pfx / *.pem / *.key and signing keys. Shell commands, URL fetches and downloads need your explicit approval, and you can scope a session to a single module or folder.

So your code does reach the LLM provider — that's unavoidable for any AI coding tool — and data handling at the model layer is governed by that provider's policy.

3) Guaranteeing the generated code doesn't include others' licensed code

We can't give that guarantee for LLM-generated code — the output can resemble existing code, and it's produced by the model provider, not by ABP. Treat generated code like any third-party snippet and review it before shipping. The commercial model providers have their own copyright / IP terms that apply to their output.

4) Token budget, alerts, and running out

Credits are purchased and tracked per organization on your Organization Management page (https://abp.io/my-organizations/). Usage is a direct passthrough of actual model usage — we don't add any markup. Studio doesn't currently expose a separate budget alert or threshold setting; when the balance runs out, AI requests stop with a clear "insufficient credits, please add credits" message until you top up. Nothing else breaks — the AI features just pause.

If your compliance team needs to go deeper on enterprise terms, data retention or a DPA, feel free to reach out to us at https://abp.io/contact and we can take it from there.

Thanks

Hi,

Exactly right — the module keeps its normal structure and its own module key, and the host generates proxies per module key (its own app plus your module), unless you ship a pre-generated client in your React package.

Glad it's clear now. Reach out anytime if more comes up as you build it.

Thanks

Hi,

Suite can only override the templates it already knows about. During generation it reads a fixed set of built-in template names and matches your overrides to them by file name, so a brand-new file like Server.AppService.GetItemsPagedInput.txt is never requested and just gets ignored. So adding a separate template file isn't possible, you can only override the existing ones (which you're already doing, in your solution's customized templates folder).

You don't need a separate file for this, though. You can define the extra input class right inside your Server.AppService.GetItemsInput.txt override, and %%property-definitions%% is filled for every class in the file, so both inputs keep their filterable properties in sync automatically.

I'd leave the default Get...Input (paged) exactly as the built-in template generates it, which keeps the default paged list and its Angular grid untouched, and just add a second non-paged class next to it:

using Volo.Abp.Application.Dtos;
using System;

namespace %%solution-namespace%%%%<if:ApplicationContractsNotExists>%%%%.AppServices%%</if:ApplicationContractsNotExists>%%.%%microservice-dtos-namespace-prefix%%%%entity-namespace%%
{
    public %%custom-code-abstract-modifier%% class Get%%entity-name-plural%%Input%%custom-code-base%% : PagedAndSortedResultRequestDto
    {
        public string? FilterText { get; set; }

        %%property-definitions%%

        public Get%%entity-name-plural%%Input%%custom-code-base%%()
        {
        }
    }

    public class Get%%entity-name-plural%%ListInput : ISortedResultRequest
    {
        public string? FilterText { get; set; }
        public string? Sorting { get; set; }

        %%property-definitions%%
    }
}

Then change your non-paged app service method (and the matching controller action) to take Get<Entities>ListInput, and leave the paged method on Get<Entities>Input. Since the non-paged input has no MaxResultCount/SkipCount, they won't show up in the generated Angular model anymore, so the required maxResultCount goes away.

Two details to watch:

  • The non-paged class needs that Sorting property, since your GetListAsync passes input.Sorting to the repository. Implementing ISortedResultRequest (as above) is the cleanest way to get it without the paging fields.
  • Keep the second class plain (no %%custom-code-abstract-modifier%% / %%custom-code-base%%). Those turn the default input into an abstract ...Base class with a generated .Extended partial inheriting it, and that partial is only created for the default input name. Put them on the new class and you'd get an abstract type with nothing inheriting from it.

Thanks

Hi,

Good questions, let me take them in order.

On the proxy and app-service ownership: the backend doesn't change between Angular and React, and you don't need to move your module's app services into the main application module. Your module keeps its own .Application.Contracts, .Application and .HttpApi layers exactly as before. When the host references your module's packages and depends on its module class, ABP brings the module's services and HTTP APIs into the host at runtime — that's the normal module dependency mechanism, and it's identical whether the host UI is Angular or React. So /api/abp/api-definition still exposes your module's endpoints the same way.

Where it's not symmetric is the frontend packaging. In the Angular world your module ships an npm package that includes the generated proxies and a provideXxx() provider, so the consumer gets the client and the routes from that package. React doesn't have that built-in module pipeline yet. On the React side the API layer is source-owned under src/lib/api on a shared Axios instance, and you regenerate the typed client from the running host (the template's generate-proxy script) rather than from a per-module package. You can still publish a React npm package that ships its own API client, but that's a custom convention you own, not an ABP-provided module integration contract. So: backend symmetry yes, frontend packaging symmetry no, and nothing needs to be merged into the main app module.

On the naming and direction: "classic" and "modern" refer to the solution template system, not an Angular-to-React migration. The classic family is the Single-Layer / Layered templates; the modern family is the ABP Studio wizard architectures (Simple Monolith, Layered Monolith, Modular Monolith, Microservice). React is available only in the modern template system, which is where the "Angular = classic, React = modern" impression comes from, but it isn't a signal that Angular is being retired. Angular is actively maintained — the roadmap includes Angular UI fixes and proxy generation improvements, and Angular SSR is already documented.

React was added as an additional first-class UI option, not a replacement. The longer-term "framework-neutral module UI" idea you're describing matches the public Hybrid UI System backlog item: re-using a module's UI across technologies so a module doesn't have to re-implement a native UI for each stack. I'd read that as the direction being explored rather than a committed plan, and it's the right place to weigh in:

  • https://github.com/abpframework/abp/issues/23102

On what to build today: if your priority is shipping a reusable module with an officially-supported frontend integration right now, Angular is the safer choice — it has the mature module-UI packaging story (module project, dev-app, npm package, provideXxx() provider, generated proxies). React is a great choice for application-owned UI, and you can make a module's React UI work with the custom npm convention from my previous reply, but ABP doesn't yet provide the same module frontend packaging/injection for React.

I wouldn't block your backend/module design on the Hybrid UI work, since it's still a backlog item with no committed version. The backend module is the same either way, so you can build it cleanly now and pick the UI packaging based on what you need to deliver — Angular if you need a distributable module frontend today, React (with the custom convention) if the UI is mainly for your own app.

Thanks

Showing 321 to 330 of 12921 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.