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.
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));
});
Configure<AbpExceptionLocalizationOptions>(options =>
{
options.MapCodeNamespace("Hub", typeof(HubResource));
options.MapCodeNamespace("CodeEntities", typeof(CodeEntitiesResource));
});
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(code)<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 {WithData} 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>:
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
messageargument passed toBusinessException(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:
throw new BusinessException("HUB:TokenExpired", "Token expired or invalid.");
throw new BusinessException("HUB:InvalidToken", "Invalid token data.");
// 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:
"Cargonerds:ApiKeys:NameAlreadyExists": "An API key with the name '{Name}' already exists."
"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
- Throw with a namespaced code:
new BusinessException("Hub:MyNewError"). - Add the key to the mapped resource's
en.json(hereHub/en.json) and any maintained culture files. Use{Token}placeholders that match your.WithData("Token", …)calls. - Prefer named
{Token}placeholders over positional{0}— the existingCodeEntity:Exception:*keys show how positional{0}silently fails to fill when the call site uses named data. - If you want a typed constant instead of a literal, add it to the (currently empty)
HubErrorCodes/CargonerdsDomainErrorCodesclass.
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" }
}
}
codeis the string you threw (clients can switch on this even when the message is unlocalized).messageis the localized text, or the literal fallback / raw code per the rules above.datacarries theWithData(...)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
*ErrorCodesclasses are empty; codes are string literals scattered across services. Grep fornew BusinessException(to enumerate them. CodeEntity(singular) is unmapped and only resolves through the defaultCargonerdsResource. Renaming the prefix to the mappedCodeEntitieswould change resolution behavior.- Positional
{0}vs namedWithData. TheCodeEntity: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:vsHub: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.jsonvsde-DE.json). The Hub and Pricing resources ship a stale neutralde.jsonalongside the registeredde-DE.json; a barederequest 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. Textsvstextscasing.Cargonerds/en.jsonuses"Texts"whileHub/en.jsonuses"texts"; ABP tolerates both.CodeEntitiesJSON is generated. ItsReadMe.mdwarns "do not add manual entries" — the file is produced fromCodeEntityLocalizations.sql. Add error-code keys to theHubresource (or the SQL source), not by hand-editingCodeEntities/*.json.
Related pages¶
- 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, notBusinessException. - Configuration Settings — the .NET configuration-loading pipeline (Spark/Azure layers and token replacement).
- REST & OData API — the full error-response envelope returned to clients.