WbizCoreX 1.2.0
dotnet add package WbizCoreX --version 1.2.0
NuGet\Install-Package WbizCoreX -Version 1.2.0
<PackageReference Include="WbizCoreX" Version="1.2.0" />
<PackageVersion Include="WbizCoreX" Version="1.2.0" />
<PackageReference Include="WbizCoreX" />
paket add WbizCoreX --version 1.2.0
#r "nuget: WbizCoreX, 1.2.0"
#:package WbizCoreX@1.2.0
#addin nuget:?package=WbizCoreX&version=1.2.0
#tool nuget:?package=WbizCoreX&version=1.2.0
WbizCoreX
WbizCoreX is the shared foundation library for the Wbiz ERP services.
It standardizes the pieces that are currently duplicated across procurement, inventory, approval, and accounting:
- tenant-aware current user context
- centralized scoped authorization from authenticated token context
- middleware that resolves user context from authenticated JWT claims or external token validation
- approval service client with automatic bearer-token forwarding
- base controller, service, and repository abstractions
- EF Core base
DbContextthat stamps tenant and audit fields - global exception middleware with a consistent API response shape
Package Structure
Auth: current-user context, token helpers, validation abstractionsAuthorization: scoped authorization service and tenant-product access validationApproval: typed approval client and DTOsFirs: typed FIRS e-invoicing service client, IRN generation, and DTOsAudit: producer SDK for publishing signed, sanitized audit events to Service Bus — a different concern fromData's entity-level stamping belowData: base entities and EF CoreDbContextsupportRepositories: generic repository contract and EF implementationServices: base application serviceMvc: base controllerMiddleware: current-user context and exception middlewareLocalization: request-localization registration and culture-fallback string helpersJson: UTC timestamp serialization convention for API payloadsDependencyInjection: registration and pipeline extensions
Intended Usage
Register shared infrastructure in each service. For production microservices, the preferred path is local JWT validation plus shared current-user/scoped-authorization handling:
builder.Services.AddAuthentication()
.AddJwtBearer();
builder.Services.AddWbizCoreX();
builder.Services.AddWbizScopedAuthorization<WBizDbContext>();
Use the middleware after UseAuthentication() so WbizCoreX can hydrate CurrentUserContext from the authenticated principal:
app.UseWbizCoreXExceptionHandling();
app.UseAuthentication();
app.UseWbizCurrentUserContext();
app.UseAuthorization();
Use the shared authorizer inside application services instead of repeating token product-scope checks:
var authorizationFailure = await scopedAuthorizationService.AuthorizeAsync<object>(new ScopedAuthorizationRequest
{
EnforceScopedAuthorization = true,
RequiredPermissions = ["materials.read"],
ForbiddenPermissionMessage = "You do not have permission to view materials."
});
if (authorizationFailure is not null)
{
return authorizationFailure;
}
The shared authorization check validates tenant-product access with the token's product_id and tenant scope, while keeping product_code as descriptive metadata on the resolved user context.
If a service wants the convenience wrapper, it can still register the shared foundation:
builder.Services.AddWbizServiceFoundation(builder.Configuration, options =>
{
options.BypassApprovalWebhook = true;
});
Use AddWbizHttpTokenValidation(...) only for services that cannot validate JWTs locally and must rely on an external token-validation endpoint. It is optional compatibility support, not the preferred production path.
If a service wants lower-level control, it can still register the pieces individually:
builder.Services.AddWbizCoreX();
builder.Services.AddWbizApprovalClient(builder.Configuration);
Build controllers and services on the shared base types:
public sealed class MaterialRequisitionService : ApplicationServiceBase
{
public MaterialRequisitionService(ICurrentUserContextAccessor currentUserContextAccessor)
: base(currentUserContextAccessor)
{
}
}
public sealed class MaterialRequisitionController : WbizControllerBase
{
public MaterialRequisitionController(ICurrentUserContextAccessor currentUserContextAccessor)
: base(currentUserContextAccessor)
{
}
}
Register the typed FIRS e-invoicing client for services that submit invoices to FIRS:
builder.Services.AddWbizFirsServiceClient(builder.Configuration);
The client binds FirsService configuration (validating that BaseUrl is an absolute URL), forwards the current user's bearer token, and exposes IRN generation plus submission-response reading through IFirsServiceClient.
Audit
Register the audit-event producer client for services that publish audit events to the audit service over Service Bus:
builder.Services.AddWbizAuditClient(builder.Configuration);
The client binds the Audit configuration section (ServiceName, FullyQualifiedNamespace, TopicName, SigningKey, Enabled), validates that ServiceName, FullyQualifiedNamespace, TopicName, and SigningKey are all set when Enabled is true, and exposes IAuditPublisher for application code to call. Each published event is sanitized (secrets and JWT/Bearer-shaped values redacted) and HMAC-signed with SigningKey before being sent. The signature covers ServiceName, TenantId, EventId, and EventTimeUtc — it authenticates the producer (proves the message came from a holder of that service's SigningKey), not the full event body, so a valid signature is not proof that ActionCode, ResultStatus, Context, Attributes, or other fields went unaltered in transit. When Enabled is false (or the section is absent), a no-op publisher is registered instead, so services can adopt this incrementally without breaking when unconfigured.
This is unrelated to Data's IAuditableEntity/AuditableEntityBase, which stamp CreatedBy/ModifiedUtc on EF Core entities on save — Audit publishes discrete business-audit events to the separate audit service, it doesn't touch entity persistence.
JSON Timestamps
Register the UTC timestamp convention so every instant a service returns is self-describing on the wire:
builder.Services.AddWbizUtcTimestamps();
SQL Server's datetime2 carries no offset, so values read back through EF Core or Dapper arrive with DateTimeKind.Unspecified. System.Text.Json then writes them with no timezone suffix - "2026-08-04T12:11:00" - and a browser parses that as local time, so every timestamp is displayed shifted by the viewer's offset. This registration writes any property whose name ends in Utc with an explicit Z and full datetime2(7) tick precision (yyyy-MM-dd'T'HH:mm:ss.fffffff'Z'), and reads incoming values back as UTC.
The Utc name suffix is the opt-in, and properties without it are left alone deliberately: stamping a calendar date such as an invoice's issue date as UTC midnight would show the previous day to a viewer west of Greenwich. A member carrying its own [JsonConverter] is also left untouched, since that is a deliberate choice by the DTO's author. The suffix is matched on the CLR member name rather than the serialized name, so a [JsonPropertyName] rename or a naming policy does not change which properties are treated as instants.
The extension configures both ConfigureHttpJsonOptions (minimal APIs) and MVC's JsonOptions, composing onto whatever resolver is already configured instead of mutating the shared instance. Services that build their own JsonSerializerOptions can apply the same behaviour directly through the UtcTimestampJson.MarkUtcTimestamps resolver modifier.
Localization
Register localization and request-culture handling (English/French, resolved from the Accept-Language header), mirroring the BaseQuc localization shape:
builder.Services.AddWbizLocalizationManagement();
Apply it early in the pipeline, before UseRouting():
app.UseWbizLocalization();
Inject IStringLocalizer<TMarker> for a host-defined marker class backed by host-owned .resx files. Use the GetLocalizedMessage extension to look up a key for the active culture and fall back to the host default culture (default "en") instead of surfacing the raw key when a translation is missing:
var message = localizer.GetLocalizedMessage("Materials.NotFound");
This repository currently contains the shared library only. The next step is to migrate procurement and approval first, since both already duplicate the same user-context and approval-integration concerns.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Azure.Identity (>= 1.21.0)
- Azure.Messaging.ServiceBus (>= 7.20.2)
- Microsoft.EntityFrameworkCore (>= 10.0.8)
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.8)
- System.IdentityModel.Tokens.Jwt (>= 8.3.1)
-
net8.0
- Azure.Identity (>= 1.21.0)
- Azure.Messaging.ServiceBus (>= 7.20.2)
- Microsoft.Bcl.Memory (>= 9.0.14)
- Microsoft.EntityFrameworkCore (>= 8.0.2)
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.2)
- System.IdentityModel.Tokens.Jwt (>= 8.3.1)
-
net9.0
- Azure.Identity (>= 1.21.0)
- Azure.Messaging.ServiceBus (>= 7.20.2)
- Microsoft.EntityFrameworkCore (>= 9.0.2)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.2)
- System.IdentityModel.Tokens.Jwt (>= 8.3.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.