Skip to content

Error Codes Reference

Cargonerds surfaces domain/application errors through ABP's BusinessException with string error codes of the form Namespace:Key. The namespace (the part before the first colon) is mapped to a localization resource; the rest is treated as a normal localization key in that resource's JSON. When the key resolves, ABP renders a user-facing message and substitutes any WithData(...) values into the {Placeholder} tokens.

This page documents the namespaces this solution registers, every error code thrown in the application/domain layers, and — importantly — which of those codes actually have localization entries (most do not). For the localization plumbing in general (resources, cultures, the translator), see Localization.

There is no central error-code catalog

All three *ErrorCodes constant classes are empty placeholder stubs — there is no enumerated catalog of codes to rely on:

  • src/Cargonerds.Domain.Shared/CargonerdsDomainErrorCodes.cs/* You can add your business exception error codes here, as constants */
  • modules/hub/src/Hub.Domain.Shared/HubErrorCodes.cs//Add your business exception error codes here...
  • modules/pricing/src/Pricing.Domain.Shared/PricingErrorCodes.cs//Add your business exception error codes here...

Codes are passed as string literals at each throw new BusinessException("…") call site. The only "typed" codes are the const strings on CodeEntityBaseService (see below). To find a code you must grep the source, not read a constants class.

How error-code localization is wired

Each *.Domain.Shared module maps its namespaces with AbpExceptionLocalizationOptions.MapCodeNamespace, and the host sets a default resource that catches everything else.

src/Cargonerds.Domain.Shared/CargonerdsDomainSharedModule.cs
Configure<AbpLocalizationOptions>(options =>
{
    options.Resources.Add<CargonerdsResource>("en")
        .AddBaseTypes(typeof(AbpValidationResource))
        .AddVirtualJson("/Localization/Cargonerds");

    options.DefaultResourceType = typeof(CargonerdsResource); // fallback for unmapped namespaces
    // ... LanguageInfo registrations ...
});

Configure<AbpExceptionLocalizationOptions>(options =>
{
    options.MapCodeNamespace("Cargonerds", typeof(CargonerdsResource));
});
modules/hub/src/Hub.Domain.Shared/HubDomainSharedModule.cs
Configure<AbpExceptionLocalizationOptions>(options =>
{
    options.MapCodeNamespace("Hub", typeof(HubResource));
    options.MapCodeNamespace("CodeEntities", typeof(CodeEntitiesResource));
});
modules/pricing/src/Pricing.Domain.Shared/PricingDomainSharedModule.cs
Configure<AbpExceptionLocalizationOptions>(options =>
{
    options.MapCodeNamespace("Pricing", typeof(PricingResource));
});

Registered namespaces

Namespace prefix Resource Resource class JSON folder Configured in
Cargonerds CargonerdsResource [LocalizationResourceName("Cargonerds")] src/Cargonerds.Domain.Shared/Localization/Cargonerds/ CargonerdsDomainSharedModule
Hub HubResource [LocalizationResourceName("Hub")] modules/hub/src/Hub.Domain.Shared/Localization/Hub/ HubDomainSharedModule
CodeEntities CodeEntitiesResource [LocalizationResourceName("CodeEntities")] modules/hub/src/Hub.Domain.Shared/Localization/CodeEntities/ HubDomainSharedModule
Pricing PricingResource [LocalizationResourceName("Pricing")] modules/pricing/src/Pricing.Domain.Shared/Localization/Pricing/ PricingDomainSharedModule

CargonerdsResource is also the DefaultResourceType — any error code whose namespace is not one of the four above (or any key the mapped resource cannot find) falls back to looking the key up in CargonerdsResource. This is exactly what makes the CodeEntity:Exception:* codes work (see Code-entity exceptions and the gotchas).

Resolution flow

flowchart TD
    A["throw new BusinessException&#40;code&#41;<br/>code = 'Namespace:Key'"] --> B{"Namespace mapped via<br/>MapCodeNamespace?"}
    B -- "Cargonerds / Hub /<br/>CodeEntities / Pricing" --> C["Look up Key in<br/>the mapped resource JSON"]
    B -- "no mapping<br/>(e.g. 'CodeEntity')" --> D["Look up code in<br/>DefaultResourceType<br/>(CargonerdsResource)"]
    C --> E{"Key present<br/>in JSON?"}
    D --> E
    E -- yes --> F["Render localized message,<br/>substitute &#123;WithData&#125; tokens"]
    E -- no --> G["Fall back to the literal<br/>message arg passed at the<br/>throw site, else the raw code"]

Known error codes

The tables below list every code thrown in the application/domain layers, the resource it resolves against, whether a localization key exists, and the call site. "Localized?" is the key distinction: a code with no JSON entry shows the literal fallback message passed to the constructor, or the raw code string if no message was passed.

Cargonerds (CargonerdsResource) — localized

These three are the canonical, fully-localized example: keys exist in Cargonerds/en.json and the translated culture files (e.g. de-DE.json).

Code Localized? English message (Cargonerds/en.json) Thrown in
Cargonerds:ApiKeys:NameAlreadyExists Yes An API key with the name '{Name}' already exists. ApiKeyAppService.cs
Cargonerds:ApiKeys:PrefixAlreadyExists Yes An API key with the prefix '{Prefix}' already exists. ApiKeyAppService.cs
Cargonerds:ApiKeys:UserNotFound Yes User '{UserId}' was not found. ApiKeyAppService.cs

Call sites: src/Cargonerds.Application/ApiKeys/ApiKeyAppService.cs (lines ~167/178/188). The {Name} / {Prefix} / {UserId} tokens are filled by the matching .WithData(...) call (see Throwing a business exception).

Code-entity exceptions

The generic CodeEntityBaseService<TCodeEntity, TDto, TService> builds its codes from a constant prefix plus nameof(...), so the literal code strings are CodeEntity:Exception:<Name>:

modules/hub/src/Hub.Application/Services/CodeEntities/CodeEntityBaseService.cs
protected const string LocalizationPrefix = "CodeEntity:Exception:";
protected const string EntityIsReadonly    = LocalizationPrefix + nameof(EntityIsReadonly);
protected const string NoEntityFound       = LocalizationPrefix + nameof(NoEntityFound);
protected const string EntityAlreadyExists = LocalizationPrefix + nameof(EntityAlreadyExists);
protected const string FailedToUpdate      = LocalizationPrefix + nameof(FailedToUpdate);
// ...
throw new BusinessException(EntityAlreadyExists).WithData("code", dto.Code);
Code Resolves via English message Thrown when
CodeEntity:Exception:EntityIsReadonly default resource Entity is Readonly create/update/delete attempted on a read-only code entity (Readonly => true by default)
CodeEntity:Exception:EntityAlreadyExists default resource An existing entity with code {0} already exists creating a code entity whose Code already exists
CodeEntity:Exception:NoEntityFound default resource No Entity with Id {0} found updating an id that does not exist
CodeEntity:Exception:FailedToUpdate default resource Failed to update entries a BulkUpdateAsync SaveChanges throws (wraps the inner exception)

Namespace mismatch: CodeEntity is not a mapped namespace

The prefix is CodeEntity: (singular). The only mapped namespaces are CodeEntities (plural), Hub, Cargonerds, and Pricing — so CodeEntity does not match MapCodeNamespace. These codes resolve only because the matching keys are present in the default CargonerdsResource (Cargonerds/en.json) and are also duplicated in CodeEntities/en.json. The {0} placeholders are positional, but the call sites pass named data (.WithData("code", …) / .WithData("id", …)), so the value is not substituted into {0} and the literal {0} is rendered. Treat these messages as approximate, not parameterized.

Hub (HubResource) — not localized

No Hub error code has a localization entry

A search across every locale file under modules/hub/src/Hub.Domain.Shared/Localization/Hub/ and /CodeEntities/ finds no key for any of the codes below. Because the key is absent, the message a client sees is:

  • the literal message argument passed to BusinessException(code, message), when one was provided; or
  • the raw code string itself (e.g. Hub:TagNotOwnedByUser), when only .WithData(...) was used and no message argument was passed.

The "Effective message" column reflects what is actually shown today, not an intended translation.

Code Casing Effective message (no JSON key) Thrown in
HUB:BlobNotFound HUB: Blob client is null for: {blobName} (literal) DocumentStorageService.cs, DocumentService
HUB:UnknownDocumentType HUB: Unknown document type: {name} (literal) DocumentAppService.cs
HUB:InvalidToken HUB: Invalid token data. (literal) DocumentAppService.cs
HUB:TokenExpired HUB: Token expired or invalid. (literal) DocumentAppService.cs
HUB:ExcelExportTokenExpired HUB: Download token not found or expired. (literal) ExcelExportAppService.cs
Hub:NoOrganizationUnit Hub: raw code (only WithData("message", …)) TagAppService.cs
Hub:AmbiguousOrganizationUnit Hub: raw code (only WithData(...)) TagAppService.cs
Hub:RootOrganizationUnitNotFound Hub: raw code (only WithData("message", …)) OrganizationUnitValidationService.cs
Hub:UnauthorizedOrganizationUnit Hub: raw code (only WithData("message", …)) CurrentFilterContextProvider.cs, OrganizationUnitValidationService.cs
Hub:DefaultOrgUnitDocumentUploadNotAllowed Hub: raw code (only WithData(...)) Cw1BookingService.cs
Hub:TagHasNoOwnership Hub: raw code (WithData("tagId", …)) TagAppService.cs
Hub:TagNotOwnedByUser Hub: raw code (WithData("tagId", …)) TagAppService.cs

Representative call sites:

modules/hub/src/Hub.Application/Services/DocumentAppService.cs
throw new BusinessException("HUB:TokenExpired", "Token expired or invalid.");
throw new BusinessException("HUB:InvalidToken", "Invalid token data.");
modules/hub/src/Hub.Application/Services/TagAppService.cs
// no message argument → the raw code "Hub:TagNotOwnedByUser" is shown to the client
throw new BusinessException("Hub:TagNotOwnedByUser").WithData("tagId", tagId);

Mixed casing: HUB: vs Hub:

Document/token codes use the upper-case prefix HUB:; org-unit/tag codes use Hub:. ABP namespace matching is case-insensitive, so both would map to HubResource — but since no keys exist either way, the practical effect is only that you must search for both spellings.

Pricing (PricingResource)

The Pricing namespace is registered, but no Pricing:* business-exception code is thrown in the current codebase, and PricingErrorCodes is empty. The Pricing module's domain-level validation uses an ABP UserFriendlyException with a free-text message instead of a coded exception — see modules/pricing/src/Pricing.Domain/Validators/QuotationCommentValidator.cs (thrown when commenting on a booked quotation). The namespace mapping exists for future use.

Un-coded business exceptions (no error code)

Many call sites throw BusinessException(message: "…") with a free-text message and no code. These never hit the localization pipeline and are shown verbatim. Examples:

  • Cw1BookingService.cs"This booking has already been deactivated.", "Bookings from the default organization unit cannot be sent to CW1."
  • OrganizationUnitAppService.cs"Cannot create a Organization Unit with the same name.", "Cannot update Default Organization Unit name."
  • UserImportAppService.cs / MagayaImporter.cs — schema/validation messages such as "Expected a JSON array at the top level.", "Uploaded file is empty."

The host OpenIddictDataSeedContributor.cs is the one place that throws with a localized key via the L[...] localizer rather than an error-code namespace, e.g. throw new BusinessException(L["NoClientSecretCanBeSetForPublicApplications"]); — these keys live in the ABP OpenIddict resource, not a Cargonerds resource.

Throwing a business exception

The pattern at the call site is: pick a code (string literal), optionally pass a fallback message, and attach data for the {Placeholder} tokens.

// Localized: key exists in Cargonerds/en.json with a {Name} token
throw new BusinessException("Cargonerds:ApiKeys:NameAlreadyExists")
    .WithData("Name", name);

The matching localization entry renders the message:

src/Cargonerds.Domain.Shared/Localization/Cargonerds/en.json
"Cargonerds:ApiKeys:NameAlreadyExists": "An API key with the name '{Name}' already exists."
src/Cargonerds.Domain.Shared/Localization/Cargonerds/de-DE.json
"Cargonerds:ApiKeys:NameAlreadyExists": "Ein API-Schlüssel mit dem Namen '{Name}' existiert bereits."

For codes without a JSON entry, always pass a sensible fallback message as the second argument, otherwise the client sees the raw code:

// Has a fallback message → client sees the message, not the code
throw new BusinessException("HUB:TokenExpired", "Token expired or invalid.");

Adding a new coded error the right way

  1. Throw with a namespaced code: new BusinessException("Hub:MyNewError").
  2. Add the key to the mapped resource's en.json (here Hub/en.json) and any maintained culture files. Use {Token} placeholders that match your .WithData("Token", …) calls.
  3. Prefer named {Token} placeholders over positional {0} — the existing CodeEntity:Exception:* keys show how positional {0} silently fails to fill when the call site uses named data.
  4. If you want a typed constant instead of a literal, add it to the (currently empty) HubErrorCodes / CargonerdsDomainErrorCodes class.

How errors surface to clients

BusinessException derives from ABP's exception hierarchy and is handled by ABP's exception middleware, which produces a structured remote-service error response with HTTP status 403 (business exceptions are treated as client-correctable by default) and a body shaped like:

{
  "error": {
    "code": "Cargonerds:ApiKeys:NameAlreadyExists",
    "message": "An API key with the name 'prod-key' already exists.",
    "details": null,
    "data": { "Name": "prod-key" }
  }
}
  • code is the string you threw (clients can switch on this even when the message is unlocalized).
  • message is the localized text, or the literal fallback / raw code per the rules above.
  • data carries the WithData(...) values.

The Hub OData/REST surface and the Blazor + React frontends consume this shape; because the code is stable, frontends can branch on it regardless of whether the message is translated. See REST & OData API for the wider error envelope and Permissions for authorization failures (which are a separate AbpAuthorizationException, not a BusinessException).

Gotchas

Most coded errors are not actually localized

Only the Cargonerds:ApiKeys:* codes and the CodeEntity:Exception:* codes have JSON entries. Every Hub:* / HUB:* code is unlocalized and surfaces as its fallback message (if any) or its raw code. Do not assume a "Meaning" implies a translated, user-facing string.

  • No central catalog. All *ErrorCodes classes are empty; codes are string literals scattered across services. Grep for new BusinessException( to enumerate them.
  • CodeEntity (singular) is unmapped and only resolves through the default CargonerdsResource. Renaming the prefix to the mapped CodeEntities would change resolution behavior.
  • Positional {0} vs named WithData. The CodeEntity:Exception:* messages use {0} but the call sites supply .WithData("code"/"id", …), so the placeholder is not filled. Match token names to data keys.
  • HUB: vs Hub: casing is inconsistent across files. Matching is case-insensitive, but search for both.
  • Free-text BusinessException(message: "…") bypasses localization entirely — those strings are English-only and shipped as-is.
  • Neutral vs specific culture (de.json vs de-DE.json). The Hub and Pricing resources ship a stale neutral de.json alongside the registered de-DE.json; a bare de request can resolve the stale file. (This does not affect Hub error codes today since none are localized, but it does affect other Hub keys.) See the Localization gotchas.
  • Texts vs texts casing. Cargonerds/en.json uses "Texts" while Hub/en.json uses "texts"; ABP tolerates both.
  • CodeEntities JSON is generated. Its ReadMe.md warns "do not add manual entries" — the file is produced from CodeEntityLocalizations.sql. Add error-code keys to the Hub resource (or the SQL source), not by hand-editing CodeEntities/*.json.
  • Localization — resources, cultures, MapCodeNamespace, the translator, and neutral-culture gotchas (this page is the deep reference that page links to).
  • Permissions — authorization failures, which throw AbpAuthorizationException, not BusinessException.
  • Configuration Settings — the .NET configuration-loading pipeline (Spark/Azure layers and token replacement).
  • REST & OData API — the full error-response envelope returned to clients.