Activities of "maliming"

Hi,

This isn't a NuGet source / feed / cache issue on your side. When you run abp update without a version, the CLI decides the "latest stable" from a version list we publish, not from NuGet directly. That list hadn't been bumped to 10.5 yet, so the CLI still saw 10.4.1 as the newest and reported everything as up to date. The 10.5 packages themselves are already on NuGet.

We're updating that list now. Give it a little while, then run abp update again and it'll pick up 10.5.

If you'd rather update right away without waiting, pin the version explicitly:

abp update -v 10.5.0 -lv 5.5.0

The -lv 5.5.0 part is needed so the LeptonX packages get updated too (10.5 ships with LeptonX 5.5.0).

Thanks

Hi,

Suite writes the generated keys into a single base localization file — that part is intentional, it doesn't fan them out to every culture file. The values it generates are English defaults ("HybridRegions": "Hybrid Regions" etc.), so it only puts them in the base file and leaves the other cultures for your real translations, instead of dropping English text into it.json / de-DE.json.

The rough edge is the file it chose. Suite looks for en.json first and writes there. Your Localization/Project folder has no en.json, so it falls back to the first *.json it finds in the folder — that's why the keys ended up in de-DE.json. It's not a deliberate pick of German, and it's not even stable across machines, since the file enumeration order depends on the OS/filesystem.

To make it predictable, add an en.json to Localization/Project:

{
  "culture": "en",
  "texts": {}
}

From then on every generation writes the new keys into en.json, and you translate them into it.json, en-GB.json and de-DE.json from there.

To set expectations: there's no option today to write into all culture files at once, or to choose the target file — the en.json file only controls which file gets the keys, the translations themselves are still manual. We'll also review this fallback so it doesn't depend on the arbitrary file order.

Thanks

Hi,

There's no supported way today to rebuild the .suite/entities metadata from your existing C# classes. But you can still use your hand-written entities in Suite relationships — the trick is that Suite's relationship generators expect a DTO (and, for master-detail, metadata) for the entity on the other end, and the default guesses a name that doesn't exist for a hand-written entity.

For the common case — a Suite entity that references an existing one (e.g. City -> Region) — use a navigation property, not master-detail, and point it at your DTO:

  1. Add the navigation property on the new entity and pick Region as the target entity.
  2. Open the Advanced tab of the navigation dialog. Under Dependent DTO, set Dto name / Dto namespace to a real DTO for Region (browse the .cs file, or type the name/namespace). If you don't already expose one, add a small RegionDto with the properties you want to show.
  3. Generate.

That's it — Region stays exactly as you wrote it (the class and the DTO are untouched), the new entity gets the foreign key and lookup, and everything compiles. The default Dto name is <Entity>Dto, so if that type doesn't exist you get the RegionDto could not be found compile error; setting it to your actual DTO is what fixes it. The generic IRepository<Region> and the entity-to-DTO mapping are resolved automatically, so as long as the DTO's properties line up with the entity you don't need to wire anything else.

Master-detail (child) relationships are different: they read the master from .suite/entities/<Master>.json and, when you generate the child, Suite regenerates the master's own code. So that path expects the master to be a Suite-managed entity — pointing it at a hand-written master will overwrite that entity's code. If you specifically need master-detail on an existing entity, plan to let Suite own it (you can use Load Entity From Database to pull the table, then move any custom logic into the *.Extended partial classes before generating, since the first generation rewrites the Suite-managed files).

On your suggestions: the UI could warn (or filter out) entities that aren't managed by Suite instead of letting you set up a relationship that then fails at generation — that's a fair point. And there's no import/rebuild of .suite metadata from existing code today either. Both make sense for long-running projects and I'll pass them to the team.

Thanks

Hi,

This message comes from the account you sign in to ABP Studio with, not from the organization license itself. Studio needs that account to be a Developer in your organization. Just having a Business license, or being only an Owner, isn't enough. Owners can manage the organization but can't download/write ABP projects, so when Studio doesn't find a developer seat for the signed-in account it falls back to the Community edition and shows the upgrade message.

Could you open https://abp.io/my-organizations and check:

  • Is the account you use in ABP Studio listed under Developers (not only under Owners)?
  • Which email is shown on that upgrade dialog in Studio? That's the account Studio is actually using.

If the account isn't under Developers, an organization owner can add it there (you can be both an Owner and a Developer at the same time). After that, sign out and sign in again in ABP Studio, a plain restart won't refresh the license, you have to re-login.

Let us know what you see and we'll take it from there.

Thanks

Hi,

Yes, that works — marking a property [Required] makes the proxy generate it without the ?.

It helps to know how it behaves on the server before applying it broadly:

  • On value types (int, bool, DateTime, Guid) [Required] is effectively a no-op. A non-nullable value type is never "missing" as far as validation is concerned, so it never triggers a required error — the only visible effect is the non-optional proxy.
  • On reference types it depends on nullable reference types. If your project has NRT enabled (the default in current ABP templates), a non-nullable string is already treated as required by ASP.NET Core model validation, even without the attribute — so adding [Required] there mostly just lines the proxy up with what the server already does. It's the nullable ones (string?) where [Required] adds a genuinely new server-side rule.

The reference-type behavior is documented here: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-10.0#non-nullable-reference-types-and-required-attribute

So in your case (Angular-only, typed proxies, values always set) marking the genuinely-required properties is safe. I'd just avoid blanket-adding it to filter/search or partial-update DTOs where some fields are meant to stay optional. There's no global switch, so it stays per property.

Thanks

Hi,

Starting with v10, the Angular proxy generator treats two things separately for each property:

  • whether it's optional (?) — decided only by whether the property is required in the API contract, i.e. it has a [Required] attribute or the C# required modifier
  • whether it's nullable (| null) — decided by whether the C# type itself is nullable

So a plain int/bool without [Required] now comes out as quantity?: number, simply because nothing in the contract marks it as required. It's no longer tied to it being a non-nullable value type, and the same ? rule now applies uniformly to int, bool, string, enum, Guid, etc. Note this is just a TypeScript optional (?), not number | null — you only get | null when the C# type itself is nullable.

This came from this issue: https://github.com/abpframework/abp/issues/24306 (changed in https://github.com/abpframework/abp/pull/24335). The old logic was inconsistent — value types, reference types, strings and enums each followed a different rule for the ?, so the same "not required" property could be optional or not depending only on its type. The new behavior keeps isRequired and isNullable as two independent flags in the API definition, the same way OpenAPI separates required from the type/nullability of a property.

If a property is always present in your responses and you want it back as quantity: number, mark it as required:

public class QuantityByDateDto
{
    [Required]
    public int Quantity { get; set; }

    [Required]
    public DateTime Date { get; set; }
}

[Required] is the cleanest way to do this. The C# required modifier produces the same result, but since it's a language-level constraint it can force you to update existing object initializers / mapping code, so for plain DTOs [Required] is usually less intrusive.

A couple of things worth keeping in mind: there's no global switch to make every non-nullable value type required — it's per property. And this only changes the generated TypeScript contract; the backend still serializes the same JSON as before, so it's not that the server suddenly omits the field. Marking the always-present fields as required also makes your API contract more accurate for any client, not just the TypeScript side.

Thanks

: )

Hi,

Glad it's working now, and nice that you also wired it through the extended partial class, that's the cleaner way to keep it regeneration-safe.

About the two requests from the Suite feature-requests thread: both are noted on our side and we do plan to implement them.

  • Not forcing a string display property on a navigation property: we'll allow picking a non-string property and leave that choice to the developer.
  • Choosing the output folder for a generated entity: we'll add support for this as well.

I can't tie either to a specific release yet, but they're planned and not forgotten.

Thanks

Hi,

That redesign is the durable fix. If you need something for 9.3.5 right now without upgrading, you can replace the inbox processor so a failing handler no longer blocks the events behind it. It skips the failing event for the current run, keeps processing the rest, and retries the failed one on the next cycle (by which point the event it depends on may already be processed):

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DistributedLocking;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Threading;
using Volo.Abp.Timing;
using Volo.Abp.Uow;

[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IInboxProcessor))]
public class SkipFailingInboxProcessor : InboxProcessor
{
    public SkipFailingInboxProcessor(
        IServiceProvider serviceProvider,
        AbpAsyncTimer timer,
        IDistributedEventBus distributedEventBus,
        IAbpDistributedLock distributedLock,
        IUnitOfWorkManager unitOfWorkManager,
        IClock clock,
        IOptions<AbpEventBusBoxesOptions> eventBusBoxesOptions)
        : base(serviceProvider, timer, distributedEventBus, distributedLock,
               unitOfWorkManager, clock, eventBusBoxesOptions)
    {
    }

    protected override async Task RunAsync()
    {
        if (StoppingToken.IsCancellationRequested)
        {
            return;
        }

        await using (var handle = await DistributedLock.TryAcquireAsync(DistributedLockName, cancellationToken: StoppingToken))
        {
            if (handle == null)
            {
                Logger.LogDebug("Could not obtain the distributed lock: " + DistributedLockName);
                try
                {
                    await Task.Delay(EventBusBoxesOptions.DistributedLockWaitDuration, StoppingToken);
                }
                catch (TaskCanceledException) { }
                return;
            }

            await DeleteOldEventsAsync();

            // Events that failed in this run. Skipping them keeps one bad event
            // from blocking the rest, and prevents a tight loop on the while below.
            var failedEventIds = new HashSet<Guid>();

            while (true)
            {
                var pendingEvents = (await GetWaitingEventsAsync())
                    .Where(x => !failedEventIds.Contains(x.Id))
                    .ToList();

                if (pendingEvents.Count <= 0)
                {
                    break;
                }

                foreach (var waitingEvent in pendingEvents)
                {
                    try
                    {
                        using (var uow = UnitOfWorkManager.Begin(isTransactional: true, requiresNew: true))
                        {
                            await DistributedEventBus
                                .AsSupportsEventBoxes()
                                .ProcessFromInboxAsync(waitingEvent, InboxConfig);

                            await Inbox.MarkAsProcessedAsync(waitingEvent.Id);

                            await uow.CompleteAsync(StoppingToken);
                        }
                    }
                    catch (OperationCanceledException) when (StoppingToken.IsCancellationRequested)
                    {
                        throw; // shutting down: don't treat cancellation as an event failure
                    }
                    catch (Exception ex)
                    {
                        failedEventIds.Add(waitingEvent.Id);
                        Logger.LogWarning(ex, $"Inbox event {waitingEvent.Id:N} failed; skipping it for this run, will retry next cycle.");
                    }
                }
            }
        }
    }
}

A few things to keep in mind, this is a stopgap, not a backport of the 10.0 policy:

  • It skips failed events within the current processor run; it has no backoff and no max-retry/discard. A failing event keeps retrying every cycle (with a warning log each time) until it succeeds, it just no longer blocks the others. The bounded retry-with-backoff and discard is what InboxProcessorFailurePolicy.RetryLater adds in 10.0.
  • The processor reads a page of waiting events (InboxWaitingEventMaxCount, default 1000) ordered by CreationTime. As long as the failing events are a small part of that page, the rest keep flowing. If failing events ever pile up to the full page size, the later events would be blocked again, so you can raise InboxWaitingEventMaxCount if needed.

The durable fix is still the order-insensitive design, this just keeps the inbox moving in the meantime.

Thanks

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

Showing 311 to 320 of 12912 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.